/*
 * @(#)HolidayManager.java
 *
 * Copyright (c) 2003 DCIVision Ltd
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of DCIVision
 * Ltd ("Confidential Information").  You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the license
 * agreement you entered into with DCIVision Ltd.
 */
package com.dcivision.setup.core;


import java.sql.Connection;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.dcivision.framework.ApplicationException;
import com.dcivision.framework.SessionContainer;
import com.dcivision.framework.TextUtility;
import com.dcivision.framework.Utility;
import com.dcivision.setup.bean.HolidayRecord;
import com.dcivision.setup.dao.HolidayRecordDAObject;

/**
 HolidayManager.java

 This class is responsible for workflow operation.

 @author          dick xie
 @company         DCIVision Limited
 @creation date   25/06/2005
 @version         $Revision: 1.4 $
 */

public class HolidayManager {

	public static final String REVISION = "$Revision: 1.4 $";

	protected Log log = LogFactory.getLog(this.getClass().getName());

	private SessionContainer sessionContainer = null;

	private Connection dbConn = null;

	private Vector scheduledJobVec = new Vector();

	/**
	 * Constructor
	 *
	 * @param sessionContainer
	 * @param conn
	 */
	public HolidayManager(SessionContainer sessionContainer, Connection conn) {
		this.sessionContainer = sessionContainer;
		this.dbConn = conn;
	}
  
  /**
   * If one of working calendar is selected, interval due date should not contain any holiday.
   * 
   * @param startDate
   * @param intervalMillisecond
   * @param workingCalendarID
   * @return end date without holiday inner.
   * @throws ApplicationException
   */
  public Timestamp getWorkingCalendarDueDateEndDate(Timestamp startDate, int intervalMillisecond, Integer workingCalendarID) throws ApplicationException {
    Timestamp endDate = null;
    Calendar endDateCalendar = Utility.timestampToCalendar(startDate);
    
    // ***** Check if need to do working calendar validate. ****************************************
    boolean ignoreWorkingCalendarValidate = false; // Ignore validate default is false.
    // If none of workingCalendar be selected, ignore working calendar validate.
    ignoreWorkingCalendarValidate = (workingCalendarID == null || (new Integer(0)).equals(workingCalendarID));
    if (ignoreWorkingCalendarValidate) {
      endDateCalendar.add(Calendar.MILLISECOND, intervalMillisecond);
      endDate = Utility.calendarToTimestamp(endDateCalendar);
      return (endDate);
    }
    
    // ***** Account the working calendar If calendarID is effective. ******************************
    WorkingCalendarManager workingCalendarManager = new WorkingCalendarManager(workingCalendarID);
    Timestamp workingCalendarStartDate = workingCalendarManager.accountWorkingCalendarStartDate(startDate);
    
    // Account the final working calendar end date.
    Timestamp workingCalendarEndDate = workingCalendarManager.accountWorkingCalendarEndDate(workingCalendarStartDate, intervalMillisecond);
    return (workingCalendarEndDate);
  }
  
  // Class major to account the working calendar.
  private class WorkingCalendarManager {
    
    Integer workingCalendarID;
    HolidayRecordDAObject holidayDAO;
    
    WorkingCalendarManager(Integer workingCalendarID) {
      this.workingCalendarID = workingCalendarID;
      this.holidayDAO = new HolidayRecordDAObject(sessionContainer, dbConn);
    }
    
    // If start date within a holiday, the working calendar start date is this holiday's end date.
    Timestamp accountWorkingCalendarStartDate(Timestamp startDate) throws ApplicationException {
      Timestamp workingCalendarStartDate = startDate;
      boolean startDateInHoliday = false;
      HolidayRecord holidayInlcudeStartDate = (HolidayRecord) this.holidayDAO.getLatestHolidayRecordByWorkingCalendarIDAndDatePoint(this.workingCalendarID,
          TextUtility.formatTimestampToDBDate(startDate));
      startDateInHoliday = Utility.isEmpty(holidayInlcudeStartDate) ? false : true;
      if (startDateInHoliday) {
        workingCalendarStartDate = holidayInlcudeStartDate.getEndDate();
      }
      return (workingCalendarStartDate);
    }
    
    Timestamp accountWorkingCalendarEndDate(Timestamp startPoint, int intervalMillisecond) throws ApplicationException {
      Timestamp endPoint = null;
      Calendar workingCalendarEndDateCalendar = Utility.timestampToCalendar(startPoint);
      workingCalendarEndDateCalendar.add(Calendar.MILLISECOND, intervalMillisecond);
      endPoint = Utility.calendarToTimestamp(workingCalendarEndDateCalendar);
      
      // Found holidays within period between startPoint and endPoint.
      List holidayList = this.holidayDAO.getDistinctHolidayRecordListByWorkingCalendarIDAndDatePoint(
          this.workingCalendarID, TextUtility.formatTimestampToDBDate(startPoint),
          TextUtility.formatTimestampToDBDate(endPoint));
      
      int intervalHolidayMillTotals = 0;
      if (!Utility.isEmpty(holidayList)) {
        for (Iterator holidays = holidayList.iterator(); holidays.hasNext();) {
          HolidayRecord holidayRecord = (HolidayRecord) holidays.next();
          intervalHolidayMillTotals += (holidayRecord.getEndDate().getTime() - holidayRecord.getStartDate().getTime()) + 1000;
        }
      }
      
      // It is clear that the current endPoint is the correct end date if there are none of holiday between this period.
      if (intervalHolidayMillTotals <= 0) {
        return (endPoint);
      }
      
      // Recursion to account the working calendar end date if interval holiday is existed.
      Timestamp nextStartPoint = endPoint;
      endPoint = this.accountWorkingCalendarEndDate(nextStartPoint, intervalHolidayMillTotals);
      
      return (endPoint);
    }
  }

	/**
	 * get the due date by start date, interval minute and working calendar ID.
	 * @param startDate
	 * @param intervalMillisecond
	 * @param workingCalendarID
	 * @return
	 * @throws ApplicationException
	 */
	public Timestamp getDueDateEndDate(Timestamp startDate,int intervalMillisecond, Integer workingCalendarID)throws ApplicationException {

		HolidayRecordDAObject holidayDAO = new HolidayRecordDAObject(this.sessionContainer, this.dbConn);

		Timestamp endDate = null;
		Calendar endDateCal = Utility.timestampToCalendar(startDate);
		endDateCal.add(Calendar.MILLISECOND, intervalMillisecond);

		if (new Integer(0).equals(workingCalendarID)) { return Utility.calendarToTimestamp(endDateCal); }

		//First Case: the StartDate in a holiday middle.
		HolidayRecord firstHoliday = (HolidayRecord) holidayDAO.getHolidayRecordObjectByWorkingCalendarIDDatePoint(workingCalendarID,
						TextUtility.formatTimestampToDBDate(startDate));
		if (!Utility.isEmpty(firstHoliday)) {
			startDate = firstHoliday.getEndDate();
		}

		//Second Case: the DueDate working days period include holiday.
		//Third Case: the DueDate in a holiday middle.
		List holidayList = holidayDAO.getHolidayRecordListByWorkingCalendarIDStartDateEndDate(
						workingCalendarID, TextUtility.formatTimestampToDBDate(startDate),
						TextUtility.formatTimestampToDBDate(Utility.calendarToTimestamp(endDateCal)));

		if (Utility.isEmpty(holidayList)) {
			endDate = Utility.calendarToTimestamp(endDateCal);
		} else {
			int intervalHolidayMill = 0;
			for (int i = 0; i < holidayList.size(); i++) {
				HolidayRecord holidayRecord = (HolidayRecord) holidayList.get(i);
				intervalHolidayMill += (holidayRecord.getEndDate().getTime() - holidayRecord.getStartDate().getTime()) + 1000;
			}
			if (intervalHolidayMill > 0) {
				endDate = Utility.calendarToTimestamp(endDateCal);
				endDate = getDueDateEndDate(endDate, intervalHolidayMill,workingCalendarID);
			}
		}
		return endDate;
	}

	public Float getWorkingHours(Timestamp startDate, Timestamp endDate,Integer workingCalendarID) throws ApplicationException {
		HolidayRecordDAObject holidayDAO = new HolidayRecordDAObject(this.sessionContainer, this.dbConn);
		Float dateDiff = null;

		List holidayList = holidayDAO.getHolidayRecordListByWorkingCalendarIDStartDateEndDate(
						workingCalendarID, TextUtility.formatTimestampToDBDate(startDate),
						TextUtility.formatTimestampToDBDate(endDate));

		if (Utility.isEmpty(holidayList)) {
			dateDiff = new Float((endDate.getTime() - startDate.getTime()) / 1000.0F / 60.0F / 60.0F);
		} else {
			int intervalHolidayMill = 0;
			for (int i = 0; i < holidayList.size(); i++) {
				HolidayRecord holidayRecord = (HolidayRecord) holidayList.get(i);
				intervalHolidayMill += (holidayRecord.getEndDate().getTime() - holidayRecord.getStartDate().getTime()) + 1000;
			}
			if (intervalHolidayMill > 0) {
				dateDiff = new Float((endDate.getTime() - (startDate.getTime() + intervalHolidayMill)) / 1000.0F / 60.0F / 60.0F);

			}
		}
		return dateDiff;
	}

}